package com.gitee.qdbp.base.controlling;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import org.apache.ibatis.mapping.SqlCommandType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
import com.gitee.qdbp.able.beans.AcceptAttrs;
import com.gitee.qdbp.able.exception.ServiceException;
import com.gitee.qdbp.able.result.ResultCode;
import com.gitee.qdbp.base.enums.OperateType;
import com.gitee.qdbp.tools.utils.VerifyTools;

public class ControllingTools {

    private static final Logger log = LoggerFactory.getLogger(ControllingTools.class);

    private static final String TABLE_FIELD_NAME = "TABLE";
    private static final String STARTS_FIELD_SUFFIX = "Starts";

    public static Field findStartsField(Class<?> clazz, String name) {
        return ReflectionUtils.findField(clazz, name + STARTS_FIELD_SUFFIX);
    }

    public static String findTableName(Class<?> clazz) {
        Field field = ReflectionUtils.findField(clazz, TABLE_FIELD_NAME);
        if (field != null && ReflectionUtils.isPublicStaticFinal(field)) {
            return ReflectionUtils.getField(field, clazz).toString();
        } else {
            return clazz.getSimpleName();
        }
    }

    public static OperateType convertOperateType(SqlCommandType cmd) {
        switch (cmd) {
        case SELECT:
            return OperateType.QUERY;
        case INSERT:
            return OperateType.CREATE;
        case UPDATE:
            return OperateType.UPDATE;
        case DELETE:
            return OperateType.DELETE;
        default:
            return OperateType.OTHER;
        }
    }

    public static Class<?> getFieldType(Object o, String fieldName) {
        Class<?> clazz = o.getClass();
        String className = clazz.getName();
        Field field = ReflectionUtils.findField(o.getClass(), fieldName);
        if (field == null) {
            throw new IllegalArgumentException("field " + fieldName + " not found in " + className);
        }

        Type type = field.getGenericType();
        if (type instanceof TypeVariable) { // 泛型
            ReflectionUtils.makeAccessible(field);
            Object value = ReflectionUtils.getField(field, o);
            return value == null ? null : value.getClass();
        } else {
            return field.getType();
        }
    }

    /** 通过反射的方式设置值 **/
    public static void doCheckAndSetValue(Object o, Field field, AcceptAttrs<?> attrs, OperateType operateType)
            throws ServiceException {
        String fn = o.getClass().getSimpleName() + "." + field.getName();
        if (attrs.isAll() && !attrs.isRequired() && operateType != OperateType.CREATE) {
            log.trace("[{}] Data isolation for {} is allow any value.", operateType, fn);
            return;
        }

        Object value = ReflectionUtils.getField(field, o);
        Object defValue = attrs.getDefaultValue();

        if (VerifyTools.isNotBlank(value)) {
            doCheckValue(o, field, value, attrs, operateType);
        } else {
            if (attrs.isAll() && !attrs.isRequired() && operateType != OperateType.CREATE) {
                log.warn("[{}] Data isolation for {} is allow null, nothing.", operateType, fn);
            } else {
                doSetValue(o, field, defValue, operateType);
            }
        }
    }

    public static void doCheckAndSetValue(Object o, Field valueField, Field startsField, AcceptAttrs<?> attrs,
            OperateType operateType) throws ServiceException {
        if (attrs.isAll() && !attrs.isRequired() && operateType != OperateType.CREATE) {
            String fn = o.getClass().getSimpleName() + "." + valueField.getName();
            log.trace("[{}] Data isolation for {}, check ok.", operateType, fn);
            return;
        }

        Object oldValue = ReflectionUtils.getField(valueField, o);
        Object oldStarts = ReflectionUtils.getField(startsField, o);
        Object defValue = attrs.getDefaultValue();

        if (VerifyTools.isNotBlank(oldValue)) {
            doCheckValue(o, valueField, oldValue, attrs, operateType);
        } else if (VerifyTools.isNotBlank(oldStarts)) {
            doCheckValue(o, startsField, oldStarts, attrs, operateType);
        } else {
            if (attrs.isAll() && !attrs.isRequired() && operateType != OperateType.CREATE) {
                String fn = o.getClass().getSimpleName() + "." + valueField.getName();
                log.warn("[{}] Data isolation for {} is allow null, nothing.", operateType, fn);
            } else {
                doSetValue(o, startsField, defValue, operateType);
            }
        }
    }

    private static void doCheckValue(Object o, Field field, Object value, AcceptAttrs<?> attrs, OperateType operateType)
            throws ServiceException {
        String fn = o.getClass().getSimpleName() + "." + field.getName();
        if (attrs.accept(value)) {
            log.trace("[{}] Data isolation for {} is {}, check ok.", operateType, fn, value);
        } else {
            log.warn("[{}] Data isolation for {} is {}, denied.", operateType, fn, value);
            throw new ServiceException(ResultCode.ACCESS_DENIED);
        }
    }

    private static void doSetValue(Object o, Field field, Object value, OperateType operateType) throws ServiceException {
        String fn = o.getClass().getSimpleName() + "." + field.getName();
        if (VerifyTools.isNotBlank(value)) {
            ReflectionUtils.setField(field, o, value);
            log.trace("[{}] Data isolation for {} to {}.", operateType, fn, value);
        } else {
            log.warn("[{}] Data isolation for {} is required, denied.", operateType, fn);
            throw new ServiceException(ResultCode.ACCESS_DENIED);
        }
    }

}
